import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

public class WorldModel implements IBouncingBallsModel {

	private final double areaWidth;
	private final double areaHeight;
	private final double gravity = 0.04;

	private double x, y, vx, vy, r;
	
	private List<Ball> balls = new ArrayList<Ball>();
	private List<Collision> collisions = new ArrayList<Collision>();
	
	private final double startEnergy;

	public WorldModel(double width, double height) {
		this.areaWidth = width;
		this.areaHeight = height;

		balls.add(new Ball(0, 1, 10, 0.7, 1.5, new Vektor(1.1, 2), Color.BLACK));
		balls.add(new Ball(1, 10, 3, 0.7, 1.5, new Vektor(0.5, 3), Color.GRAY));
		balls.add(new Ball(2, 2.5, 2.5, 2, 1, new Vektor(1, 0.5), Color.BLUE));
		balls.add(new Ball(3, 10, 1, 0.3, 1, new Vektor(1.3, 5), Color.YELLOW));
		balls.add(new Ball(4, 5, 2, 0.3, 0.1, new Vektor(2.3, 0.1), Color.GREEN.darker()));
		
		startEnergy = totalEnergyInSystem(balls);
		System.out.println("Energy at start: "+startEnergy);
	}

	@Override
	public void tick(double deltaT) {
		for (Ball ball : balls) {
			x = ball.getX();
			y = ball.getY();
			vx = ball.getV().getCathetusX();
			vy = ball.getV().getCathetusY();
			r = ball.r;
			
			if (x < r || x > areaWidth - (r+1)) {
				vx *= -1;
			}
			if (y < r || y > areaHeight - (r+1)) {
				vy *= -1;
			} else {
				vy -= gravity;
			}
			x += vx * deltaT;
			y += vy * deltaT;
			ball.update(x, y, vx, vy);
		}
		for (Ball ball : balls) {
			//check for collision
			List<Ball> tmpBalls = cloneBalls(balls);
			int index = tmpBalls.indexOf(ball);
			if(index > -1) {
				if(willOverlapInNextTick(tmpBalls, index, deltaT)) {
					ball.setColor(Color.RED);
					handleCollision(collisions);
					System.out.println("Energy after collision: "+totalEnergyInSystem(balls)+ ", Energy at start: "+startEnergy);
				} else {
					ball.setColor(ball.originalColor);
				}
			}
		}
	}
	/**
	 * The distance between the center of the two balls..
	 * @param ball1 the first ball.
	 * @param ball2 the second ball.
	 * @return The distance between the center of the two balls.
	 */
	private double distanceBetween(Ball ball1, Ball ball2) {
		double xBall1 = ball1.getX();
		double yBall1 = ball1.getY();
		double xBall2 = ball2.getX();
		double yBall2 = ball2.getY();
		
		return Math.sqrt(Math.pow((xBall1-xBall2), 2) + Math.pow((yBall1-yBall2), 2));
	}
	/**
	 * Gives the angle between the two balls.
	 * @param ball the first ball.
	 * @param otherBall the second ball.
	 * @return angle between the two balls in radians.
	 */
	private double angleBetween(Ball ball, Ball otherBall) {
		double o,a;
			o = ball.getY()-otherBall.getY();
			a = ball.getX()-otherBall.getX();
		return Math.atan2(o, a);
	}
	/**
	 * Calculates the new direction of two balls when they collied.
	 * @param ball1 a ball.
	 * @param ball2 another ball.
	 */
	private void handleCollision(List<Collision> c) {
		for (Collision collision : c) {
			Ball ball1 = collision.a;
			Ball ball2 = collision.b;
					
			//Rotate the system. Not the content of it.
			double systemRotation = angleBetween(ball1, ball2);
			ball1.getV().rotate(systemRotation);
			ball2.getV().rotate(systemRotation);

			//Recalculate new xCatheter vektor.
			double m1 = ball1.m;
			double m2 = ball2.m;
			double u1 = ball1.getV().getCathetusX();
			double u2 = ball2.getV().getCathetusX();
			double newVx1 = ((u1*(m1-m2))+(2*m2*u2))/(m1+m2);
			double newVx2 = ((u2*(m2-m1))+(2*m1*u1))/(m1+m2);
			ball1.getV().setCathetusX(newVx1);
			ball2.getV().setCathetusX(newVx2);

			//Rotate the system. Not the content of it.
			ball1.getV().rotate(-systemRotation);
			ball2.getV().rotate(-systemRotation);
		}
		collisions.clear();
	}
	/**
	 * This will work as long as the balls don't have such high velocity that they can pass through each other in under a tick.
	 * This behavior has nothing to do with velocity. Try setting the gravity to 0.001. 
	 */
	private boolean willOverlapInNextTick(List<Ball> bs, int ballNr, double deltaT) {
		//Tick the ball one step? into the future
		for (int i = 0; i < 1; i++) {
			for (Ball ball : bs) {
				double x = ball.getX();
				double y = ball.getY();
				double vx = ball.getV().getCathetusX();
				double vy = ball.getV().getCathetusY();
				double r = ball.r;

				if (x < r || x > areaWidth - r) {
					vx *= -1;
				}
				if (y < r || y > areaHeight - r) {
					vy *= -1;
				} else {
					vy -= gravity;
				}
				x += vx * deltaT;
				y += vy * deltaT;
				ball.update(x, y, vx, vy);
			}
		}
		//Check if overlapping.
		return isOverlapping(bs.get(ballNr), bs);
	}
	private boolean isOverlapping(Ball ball, List<Ball> tBalls) {
		for (Ball oBall : tBalls) {
			if(!oBall.equals(ball)) {
				if((ball.r+oBall.r) >= distanceBetween(ball, oBall)) {
					collisions.add(new Collision(balls.get(ball.id), balls.get(oBall.id)));
//					collisions.add(new Collision(ball, oBall));
				}
			}
		}
		return collisions.size() > 0;
	}
	
	private double totalEnergyInSystem(List<Ball> balls) {
		double energy = 0;
		for (Ball ball : balls)
			energy += energyOfBall(ball);
		return energy;
	}
	private double energyOfBall(Ball ball) {
		double kinetic = (ball.m*(ball.getV().getMagnitude()*ball.getV().getMagnitude()))/2;
		double potential = ball.m*Math.abs(gravity)*ball.getX();
		return kinetic+potential;
	}
	private List<Ball> cloneBalls(List<Ball> balls) {
		List<Ball> clonedBalls = new ArrayList<Ball>();
		for (Ball ball : balls) {
			clonedBalls.add(ball.clone());
		}
		return clonedBalls;
	}
	@Override
	public List<Ball> getBalls() {
		return balls;
	}
	
}
